package zerrors

import (
	"fmt"
	"io"
)

func New(message string) error {
	return &fundalmental{
		msg:   message,
		stack: callers(),
	}
}

func Errorf(format string, args ...interface{}) error {
	return &fundalmental{
		msg:   fmt.Sprintf(format, args...),
		stack: callers(),
	}
}

// 定义最基础的错误类型：
// 有表示错误信息的 msg 字段，有记录堆栈信息
// 有 Error 方法，Format 方法
type fundalmental struct {
	msg string
	*stack
}

func (f *fundalmental) Error() string {
	return f.msg
}
func (f *fundalmental) Format(s fmt.State, verb rune) {
	switch verb {
	case 'v':
		if s.Flag('+') { // 只有 %+v 才会深入到 stack 层
			io.WriteString(s, f.msg)
			f.stack.Format(s, verb)
			return
		}
		fallthrough
	case 's':
		io.WriteString(s, f.msg)
	case 'q':
		fmt.Fprintf(s, "%q", f.msg)
	}
}

// 给其它错误包给出的错误添加调用栈，
// 比如调用第三方包时返回错误，需要加上当前的调用栈信息
func WithStack(err error) error {
	if err == nil {
		return nil
	}

	// 如果传入的错误是 withCode 类型，也同样返回 withCode 类型的错误
	if e, ok := err.(*withCode); ok {
		return &withCode{
			cause: err,
			code:  e.code,
			err:   e.err,
			stack: callers(),
		}
	}

	return &withStack{
		err,
		callers(),
	}
}

// 只为给其它错误包提供的错误类型加上调用栈信息
// 因为没加额外的错误说明，所以直接把 error 做为嵌入字段，省去了多写一个 Error 方法
type withStack struct {
	error
	*stack
}

func (w *withStack) Cause() error { return w.error }

// 剥掉外层包裹、找到最底层的错误
func (w *withStack) Unwrap() error {
	if e, ok := w.error.(interface{ Unwrap() error }); ok {
		return e.Unwrap()
	}
	return w.error
}

func (w *withStack) Format(s fmt.State, verb rune) {
	switch verb {
	case 'v':
		if s.Flag('+') {
			fmt.Fprintf(s, "%+v", w.Cause())
			w.stack.Format(s, verb)
			return
		}
		fallthrough
	case 's':
		io.WriteString(s, w.Error())
	case 'q':
		fmt.Fprintf(s, "%q", w.Error())
	}
}

func WithMessage(err error, message string) error {
	if err == nil {
		return nil
	}

	return &withMessage{
		cause: err,
		msg:   message,
	}
}

func WithMessagef(err error, format string, args ...interface{}) error {
	if err == nil {
		return nil
	}

	return &withMessage{
		cause: err,
		msg:   fmt.Sprintf(format, args...),
	}
}

// 只为给其它错误包提供的错误类型加上额外的错误说明
// 因为加了额外的错误说明，原有错误的 Error 方法无法输出新加的错误说明，所以要自定义 Error 方法来输出自定义的错误说明，
// 进而不像 withStack 那样把 error 做为内嵌字段
type withMessage struct {
	cause error
	msg   string
}

func (w *withMessage) Cause() error { return w.cause }

func (w *withMessage) Unwrap() error { return w.cause }

func (w *withMessage) Error() string { return w.msg }

func (w *withMessage) Format(s fmt.State, verb rune) {
	switch verb {
	case 'v':
		if s.Flag('+') {
			fmt.Fprintf(s, "%+v\n", w.Cause())
			io.WriteString(s, w.msg)
			return
		}
		fallthrough
	case 's':
		io.WriteString(s, w.msg)
	case 'q':
		fmt.Fprintf(s, "%q", w.msg)
	}
}

// 对错误加一层包裹，包括：错误说明、堆栈信息
func Wrap(err error, message string) error {
	if err == nil {
		return nil
	}

	if e, ok := err.(*withCode); ok {
		return &withCode{
			err:   fmt.Errorf(message),
			cause: err,
			stack: callers(),
			code:  e.code,
		}
	}

	// 这里必须是先 withMessage 后 withStack，
	// 否则在返回的错误上调用 Uwrap 方法得到的是 withStack 类型的错误，不是前面传入的原始错误
	err = &withMessage{
		cause: err,
		msg:   message,
	}

	return &withStack{
		err,
		callers(),
	}
}

// 可以加格式化的错误说明
func Wrapf(err error, format string, args ...interface{}) error {
	if err == nil {
		return nil
	}

	if e, ok := err.(*withCode); ok {
		return &withCode{
			err:   fmt.Errorf(format, args...),
			cause: err,
			code:  e.code,
			stack: callers(),
		}
	}

	err = &withMessage{
		cause: err,
		msg:   fmt.Sprintf(format, args...),
	}

	return &withStack{
		err,
		callers(),
	}
}

// 单纯只为了加上错误码
func WrapC(err error, code int, format string, args ...interface{}) error {
	if err == nil {
		return nil
	}

	return &withCode{
		err:   fmt.Errorf(format, args...),
		cause: err,
		stack: callers(),
		code:  code,
	}
}

func WithCode(code int, format string, args ...interface{}) error {
	return &withCode{
		err:   fmt.Errorf(format, args...),
		code:  code,
		stack: callers(),
	}
}

// 带有错误码的错误类型
// 包含了当前格式化的错误 err、被包裹的错误 cause、错误堆栈 stack、错误码 code
type withCode struct {
	cause error
	code  int
	err   error
	*stack
}

func (w *withCode) Error() string {
	return fmt.Sprintf("%v", w)
}

func (w *withCode) Unwrap() error { return w.cause }

func (w *withCode) Cause() error { return w.cause }

// 找到错误的源头
func Cause(err error) error {
	if err == nil {
		return nil
	}

	type causer interface {
		Cause() error
	}

	for {
		cause, ok := err.(causer)
		if !ok {
			break
		}

		if cause.Cause() == nil {
			break
		}

		err = cause.Cause()
	}

	return err
}
